נניח שיש לי את המחלקות הבאות: מרובע, מקבילית, דלתון. גם מקבילית וגם דלתון אמורים לרשת ממרובע. חשבתי על שימוש ב-Traits, אבל זה סתם פתרון לא נקי ולא כיף (לזה, בכל אופן). למישהו יש רעיון?
16 תשובות
אין שום בעיה שכמה קלאסים ירשו מקלאס אחד או לירוש מקלאס שיורש ממשהו אחר.
class Rectangle extends Quadrilateral {} // מלבן
class Square extends Rectangle {} // ריבוע
class Kite extends Quadrilateral {} // דלתון
אופס. התבלבלתי.
התכוונתי למקרה כמו הבא:
ריבוע יורש ממרובע, ממקבילית, ממלבן, מדלתון וממעוין. איך אני אמור לעשות את זה? (בסופו של דבר הוא עדיין צריך לרשת מכמה מחלקות, גם אם חלקם יורשים ממחלקות אחרות, כמו ממרובע.)
זה לא דוגמה טובהבגלל
שריבוע יורש רק ממקבילית
מקבילית יורשת ממלבן
מלבן יורש ממרובע.
ובסופו של דבר יוצא שריבוע הוא גם מקבילית, גם מרובע וגם מלבן. והקוד למעלה מתאר בדיוק את המצב הזה.
היית יכול ללקת דוגמה יותר מסובכת ולקחת סוס שיכול לתפקד אצלך גם בתור חיה וגם בתור כלי תחבורה.
ושם הפתרון הוא באמת traits.
אבל הסיבה שבדוגמה עם המצולעים traits מרגישים לך מוזר, זה בגלל שפה אין לך באמת ירושה כפולה.
רק תשנה את הסדר של האיררכיה והכל יסתדר.
מלבן יורש ממקבילית, ולא מקבילית ממלבן. :)
וזה כן יוצא שהוא צריך יותר מאחד. אני בדיוק לומד את זה במתמטיקה. XD
אז תחליף אותם במקום.
מקבילית יורשת ממרובע
מלבן יורש ממקבילית
ריבוע יורש ממלבן
אני עדיין לא רואה מה לא מסתדר.
בסופו של דבר ריבוע (בתור דוגמה) צריך לרשת מכמה: גם ממעוין וגם ממלבן (ונראה לי גם מדלתון, אבל זה לא משנה כרגע).
- מקבילית יורשת ממרובע
-- מלבן יורש ממקבילית
-- מעוין יורש ממקבילית
=> ריבוע יורש ממלבן וממקבילית
זה מה שכתבת:
- מקבילית יורשת ממרובע
-- מלבן יורש ממקבילית
=> ריבוע יורש ממלבן וממקבילית
בפועל ריבוע לא יורש משני קלאסים, הוא יורש רק ממלבן
מלבן כבר יורש ממקבילית, לכן לריבוע כבר תהיה את כל הפונקציונאליות של מלבן
וגם את הפונקצינאליות של מקבילית שהוא קיבל דרך המלבן
שוב התבלבלתי. סליחה. התכוונתי שריבוע יורש ממלבן וממעוין. (ומדלתון גם אם אני לא טועה).
אז כן.
חוץ מהשאלה ההיפותטית של מה יורש ממה, יש לך משהו לעשות עם זה? איזה פונקצינאליות כלשהי?
בגלל שחוץ מהורשה בתכנות יש קונצפת שמעט מכירים אבל לדעתי הוא הרבה יותר טוב מהרושה הוא נקרא Composition
כאשר הוא דורש ממך להכיר את המונח Interface ולעבוד איתו.
מה זה אומר? שאפר שלוותר פה על הורשה לגמרי
{
public function getArea();
}
interface IMeuyan
{
public function getArea();
}
class Rectangle implements IMalben, IMeuyan
{
public function getArea()
{
return $this->w * $this->h;
}
}
או בגרסה היותר פשוטה והפחות מבלבלת:
כשיש לך סוס שמתפקד גם בתור כלי תחבורה וגם בתור חיה:
{
public function Move($miles) ;
}
interface IAnimal
{
public function Eat(Food $food);
}
class Tiger implements IAnimal
{
public function Eat(Food $food)
{
echo 'Tigriss ate ', $food, 'Yammi';
}
}
class Bycicle implements ITransport
{
public function Move($miles)
{
echo 'You ride a bycicle for ', $miles, ' miles';
}
]
class Horse implements ITransport, IAnimal
{
public function Eat(Food $food)
{
echo 'EEEEEEHHHHHHHAAAAA ', $food, 'Yammi';
}
public function Move($miles)
{
echo 'Dio, Dio ', $miles, ' to go';
}
}
class Human
{
public function RideSomewhere(ITransport $transport)
{
$transport->Move(24);
}
}
ועכשיו החלק המעניין, מה קורה אם אתה רוצה "לירוש" את הפונקציונאליות הקיימת ולהוסיף לה
ולא לממש מאפס שוב פעם - כאן לתמונה נכנס composition:
{
public function Move($miles) ;
}
interface IAnimal
{
public function Eat(IFood $food);
}
interface IGrassEatingAnimal extends IAnimal{}
class GrassEatingAnimal implements IGrassEatingAnimal
{
public function Eat(IFood $food)
{
if(!$food instanceOf GrassFood)
{
throw new \InvalidArgumentException
("This Animal eats only grass");
}
}
}
class Horse implements ITransport, IGrassEatingAnimal
{
private $animal;
public function __construct()
{
$this->animal = new \GreassEatingAnimal();
}
public function Eat(Food $food)
{
return $this->animal->Eat($food);
}
public function Move($miles)
{
echo 'Dio, Dio ', $miles, ' to go';
}
public function AddedFunction_Caress
{
echo 'hrrrhrr, Moaaaaaaaaaar';
}
}
שבמקרה הזה בפועל אין הורשה, אלה כל הפעולות ששיכות לחייה מועברות פנימה לחייה שבפנים :)
אגב, קומפוזיציה זה הסיבה העיקרית למה מי שיודע לתכנת מבין ש traits כנראה יגרמו לקוד יותר גרוע מאשר יועילו.
למזלנו, להבין traits לא יותר מדי קל, לכן מתכנתים בינוניים נמנעים מהם ולא כותבים את הקוד הגרוע שהיה מצופה להתקבל
ואילו מתכנתים מנוסים לא רואים סיבה ממשית להשתמש ב traits
לא מסכים בעניין של ה-trait.
יש מקרים שתצטרך להשתמש ב-trait.
כמו במקרה של סינגלטון. תוכל ליצור trait סינגלטון. וכל קלאס שהוא סינגלטון ישתמש ב-trait הזה.
לא תוכל להשתמש בקומפוזיציה או הורשה בשביל סינגלטון.
בעיקרון אתה גם לא תרצה ליצור סינגלטון אם אתה עובד נכון.
(סינגלטון זה שקר כזה של קוד גרוע :)
במקום שכל מחלקה שצריכה להיות סינגלטון תכיל פעולת getInstance ומשתנה פרטי instance סטטיים, אפשר להכניס את זה ל-trait.
לא ממש הבנתי, אולי כדאי לכתוב על זה מאמר או משהו כזה. :-)
בכל מקרה, חשבתי על משהו. כמה הערות לפני זה: המתודה printProperties() לא מושלמת מבחינת מה שהיא מדפיסה (פיסוק וכדו'), זו רק הדגמה לרעיון עצמו. (והשמות של המחלקות הן בהגייה עברית רק לשם הנוחות. XD)
class Meruba extends Metzula
{
public static $parents = array();
public static $properties = array("MERUBA");
}
class Malben extends Metzula
{
public static $parents = array('Meruba');
public static $properties = array("MALBEN");
}
class Meuyan extends Metzula
{
public static $parents = array('Meruba');
public static $properties = array("MEUYAN");
}
class Ribua extends Metzula
{
public static $parents = array('Malben', 'Meuyan');
public static $properties = array("RIBUA");
/*
/ The methods that use the properies
/ are going to use the $parents array.
/ The function below is an example for that.
*/
public static function printProperties()
{
echo "Properties: ";
foreach(self::$properties as $property)
{
echo $property.",";
}
foreach (self::$parents as $parent)
{
echo " Inherited from ".$parent.": ";
$parent_properties = $parent::$properties;
foreach ($parent_properties as $parent_property)
{
echo $parent_property.",";
}
}
}
}
Ribua::printProperties();
// Echoes:
// Properties: RIBUA, Inherited from Malben: MALBEN, Inherited from Meuyan: MEUYAN,
רק עכשיו קלטתי שגם אם הייתה תמיכה ב-Multiple Inheritance הייתי צריך להשתמש בשיטה הזאת, אבל בכל זאת - הלוואי שהיה, כי זה הרבה יותר סמנטי.
אה, ובקשר לשאלה למה אני רוצה את זה - זה בשביל מבחן שלי במתמטיקה. XDDD